﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SKYPE4COMLib;
using System.Threading;
using Utilities;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;

namespace SkypeApp
{
    public class SkypeBarController : IDisposable
    {
        private Keys[] hotKeysDial = { Keys.NumPad1, Keys.NumPad2, Keys.NumPad3, Keys.NumPad4, Keys.NumPad5, Keys.NumPad6, Keys.NumPad7, Keys.NumPad8, Keys.NumPad9 };
        private Keys AnsverKey = Keys.NumPad0;
        private Keys FinishCallKey = Keys.Decimal;
        private ISkypeBar View;
        SkypeService service;
        GlobalKeyboardHook hook;
        IList<DisplayUser> DisplayedUsers
        {
            get;
            set;
        }

        System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();

        private bool skipEcho = false;

        #region primary API and service related methods

        public SkypeBarController(ISkypeBar view)
        {
            View = view;
            DisplayedUsers = new List<DisplayUser>();
            hook = new GlobalKeyboardHook();
            hook.KeyDown += new KeyEventHandler(KeyHook_KeyDown);
            hook.Install();
        }

        public void Start()
        {
            service = new SkypeService();
            service.AutoAddContacts = true;
            service.OffLine += new EventHandler(service_OffLine);
            service.RefreshUsers += new EventHandler(service_RefreshUsers);
            service.CallFinished += new EventHandler(service_CallFinished);
            AddHookHotKeys();
            SetUsersCheckTimer();
        }

        public void OnFriendClicked(string handle)
        {
            WrapComException(() =>
            {
                IUser friend = service.GetFriend(handle);

                if (friend != null && !IsOffline(friend) && !service.HasActiveCalls())
                {
                    ICall call = service.PlaceCall(friend);
                }
            });
        }

        public void RefreshFriendsView()
        {
            IList<DisplayUser> users = WrapComException(() =>
            {
                var friends = service.GetFriends();
                return CreateUserDisplayCollection(friends);
            });

            if (users == null)
            {
                ClearFriendsView();
                return;
            }

            if (!ShouldUpdateAvailableUsersList(users))
                return;

            ClearFriendsView();

            DisplayedUsers = users;
            View.ShowUsers(users);
        }

        private void ClearFriendsView()
        {
            View.Clear();
        }

        private void FinishCall()
        {
            WrapComException(() => service.TryFinishCall());
        }

        private void Ansver()
        {
            WrapComException(() => service.TryPartnerAnsver());
        }

        public void Dispose()
        {
            hook.Dispose();
        }

        #endregion

        #region event handlers

        void service_CallFinished(object sender, EventArgs e)
        {
            WrapComException(() => service.HideSkypeWindow());
            View.Focus();
        }

        void service_RefreshUsers(object sender, EventArgs e)
        {
            RefreshFriendsView();
        }

        void timer_Tick(object sender, EventArgs e)
        {
            RefreshFriendsView();
        }

        void service_OffLine(object sender, EventArgs e)
        {
            ClearFriendsView();
        }

        void KeyHook_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == AnsverKey)
                Ansver();
            else
            {
                if (e.KeyCode == FinishCallKey)
                {
                    FinishCall();
                }
                else
                {
                    var user = DisplayedUsers.FirstOrDefault(u => u.HotKey == e.KeyCode);
                    if (user != null)
                        OnFriendClicked(user.Handle);
                }
            }
        }

        #endregion

        #region helper stuff

        private void WrapComException(Action action)
        {
            WrapComException<object>(() => { action(); return null; });
        }

        private T WrapComException<T>(Func<T> function)
        {
            try
            {
                return function();
            }
            catch (COMException)
            {
                return default(T);
            }
        }

        #endregion

        private void SetUsersCheckTimer()
        {
            timer.Tick += new EventHandler(timer_Tick);
            timer.Interval = 5000;
            timer.Start();
        }

        private bool ShouldUpdateAvailableUsersList(IList<DisplayUser> newToCompare)
        {
            var oldFriends = DisplayedUsers;

            if (oldFriends.Count != newToCompare.Count)
                return true;

            foreach (var newUser in newToCompare)
            {
                if (!oldFriends.Any(user => user.Handle == newUser.Handle && user.Enabled == newUser.Enabled))
                    return true;
            }

            return false;
        }

        private List<DisplayUser> CreateUserDisplayCollection(IList<IUser> friends)
        {
            var displayUsers = new List<DisplayUser>();
            int number = 0;
            foreach (IUser friend in friends)
            {
                if (skipEcho && friend.Handle == "echo123")
                    continue;

                bool currentDisabled = (friend.OnlineStatus == TOnlineStatus.olsOffline
                    || friend.OnlineStatus == TOnlineStatus.olsNotAvailable
                    || friend.OnlineStatus == TOnlineStatus.olsUnknown);

                if (ShouldHideUnavailableFriends(friends) && currentDisabled)
                {
                    number++;
                    continue;
                }

                displayUsers.Add(new DisplayUser()
                {
                    Handle = friend.Handle,
                    Enabled = !currentDisabled,
                    DisplayText = GetUserCaption(friend, number),
                    HotKey = GetHotKey(number)
                });

                number++;
            }
            return displayUsers;
        }

        private Keys? GetHotKey(int userNo)
        {
            return userNo < hotKeysDial.Length ? hotKeysDial[userNo] : (Keys?)null;
        }

        private string GetUserCaption(IUser friend, int userNo)
        {
            Keys? hotKey = GetHotKey(userNo);
            string keyText = hotKey.HasValue ? string.Format("[{0}] - ", (int)hotKey.Value - (int)Keys.NumPad0) : string.Empty;
            return keyText + (string.IsNullOrWhiteSpace(friend.DisplayName) ? friend.FullName : friend.DisplayName);
        }

        protected bool IsOffline(IUser friend)
        {
            return friend.OnlineStatus == TOnlineStatus.olsOffline
                    || friend.OnlineStatus == TOnlineStatus.olsNotAvailable
                    || friend.OnlineStatus == TOnlineStatus.olsUnknown;
        }

        private bool ShouldHideUnavailableFriends(IList<IUser> friends)
        {
            return friends.Count > 9;
        }

        private void AddHookHotKeys()
        {
            hook.ClearKeys();
            hook.AddHookKey(AnsverKey);
            hook.AddHookKey(FinishCallKey);
            foreach (var key in hotKeysDial)
                hook.AddHookKey(key);
        }
    }
}
